from cv2 import (
    Mat, cvtColor,
    COLOR_BGR2GRAY,
    imread, IMREAD_GRAYSCALE, matchTemplate, TM_CCOEFF_NORMED, minMaxLoc, IMREAD_GRAYSCALE, threshold, THRESH_BINARY, THRESH_OTSU, rectangle, imwrite,resize, erode
)
import numpy as np
# from skimage.metrics import structural_similarity as compare_ssim
from concurrent.futures import ThreadPoolExecutor, Future
from typing import List, Optional
from typing_extensions import TypedDict, Annotated
from get_logger import get_check_logger, get_error_logger
from extends.email import thread_pool_send_email
from config import get_settings
from datetime import datetime
from copy import deepcopy
from PIL import Image

import concurrent.futures
import pytesseract
import os

__all__ = ["CheckVideo"]

# check_logger = get_check_logger()
error_logger = get_error_logger()

settings = get_settings()


class CheckResults(TypedDict):
    check_hhs_windows: bool # "都为false则没问题"
    check_hhs_cursor: bool  # "False为不存在鼠标"
    datetime_ocr: float     # "识别出的时间"


class Mix:
    frame: Optional[Mat] = None
    previous: bool = False  # 上一次的数据
    qualified = True # 合格

class CheckWindow(Mix):
    def __init__(self):

        self.MMI_WINDOW_SSIM_THRESHOLD = 0.9
        self.TOP_LEFT_CORNER_GRAYSCALE_TEMPLATE = imread(
            os.path.join(settings.BASE_DIR, "static", "top_left_corner_grayscale_template.png"), IMREAD_GRAYSCALE)
        self.TOP_RIGHT_CORNER_GRAYSCALE_TEMPLATE = imread(
            os.path.join(settings.BASE_DIR, "static", "top_right_corner_grayscale_template.png"), IMREAD_GRAYSCALE)
        self.BOTTOM_LEFT_CORNER_GRAYSCALE_TEMPLATE = imread(
            os.path.join(settings.BASE_DIR, "static", "bottom_left_corner_grayscale_template.png"), IMREAD_GRAYSCALE)
        self.BOTTOM_RIGHT_CORNER_GRAYSCALE_TEMPLATE = imread(
            os.path.join(settings.BASE_DIR, "static", "bottom_right_corner_grayscale_template.png"), IMREAD_GRAYSCALE)
        self.Top_Left_Corner = [[0, 25], [0, 28]],
        self.Top_Right_Corner = [[0, 25], [1892, 1920]],
        self.Bottom_Left_Corner = [[1055, 1080], [0, 28]],
        self.Bottom_Right_Corner = [[1055, 1080], [1892, 1920]],

        self.MMI_WINDOW_CORNER_AREA = [
            self.Top_Left_Corner[0],
            self.Top_Right_Corner[0],
            self.Bottom_Left_Corner[0],
            self.Bottom_Right_Corner[0],
        ]

        self.detect_mmi_window_futures: Annotated[list[bool], "都为flase则没问题"]

    def start(self, frame):
        self.detect_mmi_window_futures = []

        for index, coordinate in enumerate(self.MMI_WINDOW_CORNER_AREA):  # 屏幕的四个角区域
            start_x = coordinate[0][0]
            start_y = coordinate[0][1]
            end_x = coordinate[1][0]
            end_y = coordinate[1][1]
            self.detect_mmi_window_futures.append(
                self.detect_mmi_window(
                    index,
                    cvtColor(frame[
                        start_x: start_y,
                        end_x: end_y
                    ], COLOR_BGR2GRAY)
                )
            )
        return any(self.detect_mmi_window_futures)  # true为不通过

    # def detect_mmi_window(self, corner_position, area):
    #     """检测屏幕是否是全屏
    #     """
    #     gray_corner_template: Mat = \
    #         self.TOP_LEFT_CORNER_GRAYSCALE_TEMPLATE  # 左上角灰色

    #     if corner_position == 1:
    #         gray_corner_template = \
    #             self.TOP_RIGHT_CORNER_GRAYSCALE_TEMPLATE  # 右上角

    #     elif corner_position == 2:
    #         gray_corner_template = \
    #             self.BOTTOM_LEFT_CORNER_GRAYSCALE_TEMPLATE  # 左下角

    #     elif corner_position == 3:
    #         gray_corner_template = \
    #             self.BOTTOM_RIGHT_CORNER_GRAYSCALE_TEMPLATE  # 右下角

    #     corner_mssim: float = compare_ssim(
    #         area,
    #         gray_corner_template,
    #         gaussian_weights=True,
    #         sigma=1.5,
    #         use_sample_covariance=False
    #     )

    #     if corner_mssim < self.MMI_WINDOW_SSIM_THRESHOLD:
    #         if self.previous == False:
    #             # thread_pool_send_email(settings.TO_ADDR, "HHS", "检测到屏幕非全屏")
    #             error_logger.error("检测到屏幕非全屏")
    #         self.previous = True
    #         return True

    #     if True in self.detect_mmi_window_futures:
    #         self.previous = True
    #     else:
    #         self.previous = False
    #     return False


class CheckCursor(Mix):
    def __init__(self):

        if not hasattr(self, "CURSOR_BINARY_TEMPLATE_GRAYSCALE"):
            gray_cursor = imread(os.path.join(
                settings.BASE_DIR, "static", "cursor.png"), IMREAD_GRAYSCALE)
            self.CURSOR_BINARY_TEMPLATE_GRAYSCALE = gray_cursor
            _, self.CURSOR_BINARY_TEMPLATE_GRAYSCALE = threshold(
                gray_cursor, 0, 255, THRESH_BINARY | THRESH_OTSU)

        self.CURSOR_THRESHOLD = 0.5

        self.CURSOR_TEMPLATE_HEIGHT: int = \
            self.CURSOR_BINARY_TEMPLATE_GRAYSCALE.shape[0]

        self.CURSOR_TEMPLATE_WIDTH: int = \
            self.CURSOR_BINARY_TEMPLATE_GRAYSCALE.shape[-1]

        self.CURSOR_AREAS = [
            [[125, 237], [377, 654]],
            [[125, 348], [629, 1176]],
            [[125, 242], [1176, 1516]],
            [[323, 361], [1166, 1492]],
            [[351, 434], [675, 1267]],
            [[434, 551], [676, 1218]],
            [[551, 670], [675, 1161]],
            [[670, 764], [675, 1088]],
            [[764, 912], [675, 1033]],
        ]

        self.debug_cursor_position: List
        self.previous: Annotated[bool, "False为不存在鼠标"]

    def start(self, frame):
        gray_frame = cvtColor(frame, COLOR_BGR2GRAY)
        self.debug_cursor_position = []
        cursor_detected = False

        for coordinate in self.CURSOR_AREAS:
            if self.detect_cursor_on_screen(
                coordinate,
                gray_frame[
                    coordinate[0][0]: coordinate[0][1],
                    coordinate[1][0]: coordinate[1][1]
                ]
            ):
                cursor_detected = True

        if cursor_detected == True and self.previous == False:
            self.previous = True
            # thread_pool_send_email(settings.TO_ADDR, "HHS", "检测到光标")
            error_logger.error("检测到光标")
        elif cursor_detected == False:
            self.previous = False
        return self.previous

    def detect_cursor_on_screen(self, position, area) -> bool:
        """检测光标
        """
        cursor_match_result = \
            matchTemplate(
                area,
                self.CURSOR_BINARY_TEMPLATE_GRAYSCALE,
                TM_CCOEFF_NORMED
            )

        (cursor_min_value,
         cursor_max_value,
         cursor_min_location,
         cursor_max_location) = minMaxLoc(cursor_match_result)

        if cursor_max_value > self.CURSOR_THRESHOLD:
            self.debug_cursor_position.append(
                [list(cursor_max_location)[0] + position[1][0],
                    list(cursor_max_location)[1] + position[0][0],
                    (list(cursor_max_location)[0]
                     + position[1][0]
                     + self.CURSOR_TEMPLATE_WIDTH),
                    (list(cursor_max_location)[1]
                     + position[0][0]
                     + self.CURSOR_TEMPLATE_HEIGHT),]
            )

            return True

        return False
    def picture_frame(self, frame):
        """画框
        """
        for position in self.debug_cursor_position:
            rectangle(
                frame,
                (position[0], position[1]),
                (position[2], position[3]),
                255,
                2
            )


class DatetimeOcr(Mix):
    def __init__(self) -> None:
        self.x1, self.y1, self.x2, self.y2 = 1775, 131, 1914, 179  # (正常大小)左上,右下
        pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'
        self.previous = False
        self.custom_config = r'--psm 6 -c tessedit_char_whitelist=0123456789:'

    def extract_time_with_length(self, time_str, current_time: datetime) -> datetime:
        # 去除所有非数字字符
        digits = ''.join(filter(str.isdigit, time_str))

        # 检查提取的数字长度
        digit_len = len(digits)
        if digit_len == 5:  # 5位数字，假设是HHMMSS
            hours, minutes, seconds = map(
                int, [digits[:1], digits[1:3], digits[3:]])
            self.x1, self.y1 = 1775, 131 # (正常大小)左上
        elif digit_len == 6:  # 6位数字，假设是HHMMSS
            hours, minutes, seconds = map(
                int, [digits[:2], digits[2:4], digits[4:]])
        elif digit_len == 7:
            hours, minutes, seconds = map(
                int, [digits[:3], digits[3:5], digits[5:]])
            self.x1, self.y1 = 1791, 131  # (往右移动一位数字)左上
        else:
            raise Exception(f"过滤时间格式错误：{digits}")
        ocr_time = deepcopy(current_time)
        return ocr_time.replace(hour=hours, minute=minutes, second=seconds)

    def calculate_timestamp(self, datetime_text: str) -> Optional[float]:
        try:
            datetime_list = datetime_text
            current_time = datetime.now()
            ocr_time = self.extract_time_with_length(
                datetime_list, current_time)
            time_difference = current_time - ocr_time
            timestamp = time_difference.total_seconds()
            self.previous = True
            self.qualified = True
        except Exception as e:
            error_msg = f"解析时间异常:[{datetime_text}] Detail: {e}"
            if self.qualified == True: #上一次是合格 
                error_logger.error(error_msg)
                image_name = current_time.strftime("%Y-%m-%d %H:%M:%S") + f"[{datetime_text}].jpg"
                imwrite(os.path.join(settings.BASE_DIR, "logs", "error_logger", image_name), self.frame)
                thread_pool_send_email(settings.TO_ADDR, "PHD-LOCO",error_msg)
            self.previous = False
            self.qualified = False
            timestamp = ""
        return timestamp

    def start(self, frame):
        self.frame = frame
        pil_image = self.frame_process(frame)
        datetime_text = self.get_datetime(pil_image)
        return self.calculate_timestamp(datetime_text)

    def frame_process(self, frame):
        """接取时间识别区域, 并放大处理"""
        roi = frame[self.y1:self.y2, self.x1:self.x2]
        roi_enlarged = resize(roi, (0,0), fx=3, fy=3)
        roi_erode = erode(roi_enlarged, np.ones((3, 3), np.uint8), iterations=1)
        gray_roi = cvtColor(roi_erode, COLOR_BGR2GRAY)
        _, binary_roi = threshold(
            gray_roi, 0, 255, THRESH_BINARY | THRESH_OTSU)
        pil_image = Image.fromarray(binary_roi)
        return pil_image


    def get_datetime(self, frame):
        return pytesseract.image_to_string(frame, config=self.custom_config)


class CheckVideo:
    def __init__(self):
        self.checkers = {}
        self.results = {}
        self.check_thread_pool_list: List[Future] = []
        self.check_thread_pool: ThreadPoolExecutor = ThreadPoolExecutor(
            max_workers=3)

        if settings.ENABLED_CHECK_WINDOWS:
            self.add_checker("check_hhs_windows", CheckWindow())

        # if settings.ENABLED_CHECK_CURSOR:
            # self.add_checker("check_hhs_cursor", CheckCursor())

        if settings.ENABLED_TATE_TIME_OCR:
            self.add_checker("datetime_ocr", DatetimeOcr())

    def add_checker(self, name, checker):
        self.checkers[name] = checker
        self.results[name] = None

    def start_checkers(self, frame):
        """同步版本启动检测
        """
        for name, checker in self.checkers.items():
            if hasattr(checker, "start"):
                self.results[name] = checker.start(frame)
        return self.results

    def thread_start_checkers(self, frame):
        """使用多线程启动检测
        """
        # 初始化一个字典来存储Future和其对应检查器名字的关系
        future_to_name = {}
        self.check_thread_pool_list = []

        for name, checker in self.checkers.items():
            if hasattr(checker, "start"):
                future = self.check_thread_pool.submit(checker.start, frame)
                future_to_name[future] = name  # 记录Future与检查器名字的对应关系
                self.check_thread_pool_list.append(future)

        # 等待所有Future完成并收集结果
        for future in concurrent.futures.as_completed(self.check_thread_pool_list):
            name = future_to_name[future]  # 根据Future找到对应的检查器名字
            try:
                self.results[name] = future.result()  # 正确地存储每个检查器的结果
            except Exception as exc:
                error_logger.error(f"Checker {name} failed with error: {exc}")
                self.results[name] = None  # 或者根据需要处理异常情况
        return self.results

    def close(self):
        if self.check_thread_pool:
            self.check_thread_pool.shutdown()
